//
/*   Helper code for Matrix * Vector operation program
*    Copyright (C) 2009 Goffredo Marocchi
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License along
*    with this program; if not, write to the Free Software Foundation, Inc.,
*    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/


#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <time.h>
#include <string.h>

#include <cutil_inline.h>
#include <cuda.h>
#include <cutil_math.h>
#include <vector_types.h>

#include "IGGS_CUDA_defines.h"
#include "MatTest_helper.h"

////////////////////////////////////////////////
// General Purpose Functions
////////////////////////////////////////////////

/**\brief This tests if two vectors are equal or not
 *
 * Both vectors must have the same length.\n
 *
 * \param a first vector, float*
 * \param b second vector, float*
 *
 * \param SIZE length of both vectors, const int
 *
 * \return bool
 */
bool vectorEQ (float* a, float* b, const int SIZE) {

	if (null == a || null == b) return false;

	for (int i = 0; i < SIZE; i++) {

		if ( a[i] != b[i] ) {

			send_msg ("\nIndex: %d\n", i);
			send_msg ("a[%d]: %f , b[%d]: %f\n", i, a[i], i, b[i]);
			return false;

		}

	}

	return true;
}

/**\brief This tests if two vectors are equal or not
 *
 * Both vectors must have the same length.\n
 *
 * \param a first vector, float*
 * \param b second vector, float*
 *
 * \param SIZE length of both vectors, const int
 *
 * \return void
 */
extern "C" void vector_eq_test_ (float* a, float* b, const int SIZE) {

	bool test = true;

	if (null == a || null == b) test = false;

	for (int i = 0; i < SIZE; i++) {

		if ( a[i] != b[i] ) {

			err_msg ("\nIndex: %d\n", i);
			err_msg ("a[%d]: %f , b[%d]: %f\n", i, a[i], i, b[i]);
			test = false;

		}

	}

	if ( false == test ) {

		err_msg("\n(ERR) The two vectors are not equal :(.\n");

	}

	else {

		send_msg("\n(OK) The two vectors are equal :).\n");

	}

	return;
}


/**\brief This function calculates a random floating point number (single precision)
 *
 * \param low minimum value of the chosen interval, float
 * \param high maximum value of the chosen interval, float
 * \return float
 */
float RandFloat(float low, float high){
	float t = (float)rand() / (float)RAND_MAX;
	return (1.0f - t) * low + t * high;
}

/**\brief This function dynamically allocates a matrix
 * More convoluted method, but the final matrix can be nicely used as a fully working 2D array (mat[][]).
 *
 * \param mat the pointer to pointer to pointer variable is used to store the matrix, float***
 * \return void
 */
void malloc_MatrixFPv1 ( float*** mat, const int rows, const int columns) {

	int i,index;

	(*mat) = (float **) calloc (rows, sizeof(float *)); //calloc does the same thing as malloc but
	//inits memory to 0

	*(*mat) = (float *) calloc (rows * columns, sizeof (float));

	for (i = 0; i < rows; i++) {

		for (index = 0; ; index = 0 + i*columns) {

			*((*mat) + i) = ((**mat) + index);

		}

	}

	return;

}


/**\brief This function dynamically allocates a matrix
 *
 * Se adoperiamo questa v2 della funzione che alloca una matrice dinamicamente allora\n
 * useremo in seguito per accedere alla matrice, implementata con un largo array 1D, la seguente macro:\n
 * \n
 * 	index(i, j , n_columns) ((j) + ((i) * (n_columns))) //row-major matrix\n
 * \n
 * gia' definita in precedenza.\n
 *
 * \param mat pointer to pointer variable used to store the matrix, float**
 * \param rows number of rows of the matrix, const int
 * \param columns number of columns of the matrix, const int
 * \return void
 */
void malloc_matrixFPv2 ( float** mat, const int rows, const int columns) {

	(*mat) = (float *) calloc (rows * columns, sizeof (float));

	return;

}

/**\brief This function starts the cutil based timer
 * \param t reference to the timer object, unsigned int*
 * \return void
 */
void start_timer (unsigned int* t) {

	//cutilSafeCall(cutCreateTimer(&timer_t1));

	cutilCheckError(cutCreateTimer(t));
	cutilCheckError(cutStartTimer((*t)));

	return;
}

/**\brief This function stops the cutil based timer and converts the elapsed time into milliseconds
 * \param t reference to the timer object, unsigned int
 * \param t_ms reference to the variable used to store the timer's value into milliseconds, float*
 * \return void
 */
void stop_timer (unsigned int t, float * t_ms) {

	cutilCheckError(cutStopTimer(t));
	(*t_ms) = cutGetTimerValue(t);
	cutilCheckError(cutDeleteTimer(t));

	return;
}

/**\brief This function prints to STDOUT the amount of total device memory and the amount of free device memory
 * \return void
 */
extern "C" void gpu_print_free_memory_ () {

	//The following code is able to display the amount of free device memory at this current moment
    unsigned int total_memory;
    unsigned int free_memory;
    CUresult r;
    r = cuInit(0);
    send_msg("Init result     : %d\n",r); //the result should be 0
    r = cuMemGetInfo(&free_memory,&total_memory);
    send_msg("Mem info result : %d",r); //the result should be 0
    send_msg("Total memory: %f MB",(float)total_memory/(1024*1024));
    send_msg("Free memory: %f MB", (float)free_memory/(1024*1024));

	return;

}

/**\brief This function inits the CUDA device and prints to STDOUT the current CUDA device properties
 * \return void
 */
extern "C" void gpu_init_ () {

	//CUDA device set-up and initialization
    cudaDeviceProp device_properties;
    int dev_id = cutGetMaxGflopsDeviceId();
    cudaGetDeviceProperties( &device_properties, dev_id );

#ifdef _DEBUG
    //the CUDA device does not need to be manually set, default CUDA device (0) already selected.
    int gflops = 0;
    gflops = device_properties.multiProcessorCount * device_properties.clockRate;

    send_msg("Using device %d: %s", dev_id, device_properties.name);
    send_msg ("# of SM: %d", device_properties.multiProcessorCount);
    send_msg ("\nClock Speed: %d MHz", device_properties.clockRate/1000);
    send_msg ("\nPeak FP performance (MADD+MUL co-issue): %d GFLOPS", (gflops*8*3/1000000));
    send_msg ("\nPeak FP performance (MADD single-issue): %d GFLOPS\n", (gflops*8*2/1000000));
	//MADD+MUL co-issue -- 2 + 1 FP ops/cycle
#endif
	return;
}
